﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;

namespace ClownStopper
{
 
    public interface IMessageServer
    {
        int ListenPort { get; set; }

        event ServerRegisterEvent OnRegisterClient;

        void Initialize();
    }


    public class MessageServer : IMessageServer
    {
        #region Events

        

        private event OnReceiveServerMessageDelegate MOnReceiveMessage;

        public event OnReceiveServerMessageDelegate OnReceiveMessage
        {
            add { MOnReceiveMessage += value; }
            remove { MOnReceiveMessage -= value; }
        }

        private event ServerRegisterEvent MOnRegisterClient;
        public event ServerRegisterEvent OnRegisterClient
        {
            add { MOnRegisterClient += value; }
            remove { MOnRegisterClient -= value; }
        }

        private event OnErrorDelegate ServerReceiveError; 
        public event OnErrorDelegate OnServerReceiveError
        {
            add { ServerReceiveError += value; }
            remove { ServerReceiveError -= value; }
        }
        #endregion

        #region private members
        private readonly Dictionary<int, IMessage> _possibleMessages;
        private Socket _mListenerSocket; 
        #endregion

        public MessageServer(int listenPort, string group)
        {
            _possibleMessages = new Dictionary<int, IMessage>();
            _possibleMessages[((int)MessageType.RegisterMessage)] = new RegisteredClient();
            _possibleMessages[((int)MessageType.UnregisterMessage)] = new UnregisteredClient();
            _possibleMessages[((int)MessageType.TextDataMessage)] = new TextMessage();
            _possibleMessages[((int)MessageType.BingoMessage)] = new BingoMessage();

            ListenPort = listenPort;
            ClientDetailsGroups = new Dictionary<string, Collection<IMessageClientDetails>>();
            GroupClientsDetails = new Collection<IMessageClientDetails>();
            Group = group;
            
        }

        #region Properties
        public Dictionary<String, Collection<IMessageClientDetails>> ClientDetailsGroups { get; set; }

        public Collection<IMessageClientDetails> GroupClientsDetails { get; set; }

        public String Group { get; set; }

        public String LocalIPAddress { get; set; }

        public int ListenPort { get; set; } 
        #endregion

        public void Initialize()
        {
            IPHostEntry hostEntry = Dns.GetHostEntry( /*"localhost"*/Dns.GetHostName()); //Dns.Resolve("localhost").AddressList[0];

            if (hostEntry.AddressList.Length <= 0)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs("ErrorNoAvailableIPAddress"));
                
                return;
            }

            IPAddress localAddress = null;
            for (int i = 0; i < hostEntry.AddressList.Length; ++i)
            {
                if (hostEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
                {
                    localAddress = hostEntry.AddressList[i];
                }
            }

            if (localAddress == null)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs("ErrorNoAvailableIPAddress"));
                return;
            }

            try
            {
                //create a listening socket
                if (_mListenerSocket == null)
                {
                    _mListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                }
                var localIP = new IPEndPoint(localAddress /*IPAddress.Any*/, ListenPort);
                _mListenerSocket.Bind(localIP);
                _mListenerSocket.Listen(50); //TODO -> ponder which is the best value to use here
                _mListenerSocket.BeginAccept(new AsyncCallback(OnHandleClientConnection), null);
            }
            catch (SocketException ex)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs("Unable to create the socket : " + ex.Message));
            }
            catch (ObjectDisposedException ex)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs("The socket was forcefully closed : " + ex.Message));
            }
            catch (Exception ex)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs(ex.Message));
            }
        }

        #region receive Messages from Client
        protected void OnHandleClientConnection(IAsyncResult asyncResult)
        {
            try
            {
                Socket workerSocket = _mListenerSocket.EndAccept(asyncResult);

                try
                {
                    var dataStatus = new TxRxPacket(workerSocket);
                    workerSocket.BeginReceive(dataStatus.mDataBuffer, 0, dataStatus.mDataBuffer.Length, SocketFlags.None, new AsyncCallback(OnHandleClientData), dataStatus);
                }
                catch (SocketException ex)
                {
                    if (ServerReceiveError != null)
                        ServerReceiveError.Invoke(this, new ErrorEventArgs(ex.Message));
                }
                catch (Exception ex)
                {
                    if (ServerReceiveError != null)
                        ServerReceiveError.Invoke(this, new ErrorEventArgs(ex.Message));
                    
                }

                _mListenerSocket.BeginAccept(new AsyncCallback(OnHandleClientConnection), null);
            }
            catch (ObjectDisposedException ex)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs("Socket has been closed : " + ex.Message));
            }
            catch (Exception ex)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs(ex.Message));
            }
        }

        /// <summary>
        /// Parses the message.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <returns></returns>
        /// <by>Dan Turco</by>
        /// <created>2/21/2011</created>
        /// <revised></revised>
        private IMessage ParseMessage(Byte[] data)
        {
            string error;
            foreach (var messageByType in _possibleMessages)
            {
                if (messageByType.Value.Parse(data, out error))
                {
                  return messageByType.Value.Clone();
                }
                if (!string.IsNullOrEmpty(error))
                {
                    if (ServerReceiveError != null)
                        ServerReceiveError.Invoke(this, new ErrorEventArgs("Parse Error: " + error));
                }
            }
            return null; //TODO -> use this instead? : new EmptyMessage();
        }

        protected void OnHandleClientData(IAsyncResult asyncResult)
        {
            try
            {
                var dataStatus = (TxRxPacket)asyncResult.AsyncState;

                dataStatus.mCurrentSocket.EndReceive(asyncResult);
                dataStatus.StoreCurrentData();
                IMessage rxMessage = ParseMessage(dataStatus.mStoredBuffer);

                if (rxMessage == null)
                {
                    //receive the rest of the message
                    dataStatus.mCurrentSocket.BeginReceive(dataStatus.mDataBuffer, 0, dataStatus.mDataBuffer.Length, SocketFlags.None, new AsyncCallback(OnHandleClientData), dataStatus);
                    return;
                }

                //handle the message (which can either be register or unregister)
                //send response message if needed
                switch (rxMessage.Type)
                {
                    case ((int)MessageType.RegisterMessage):
                        {
                            if (!(GroupClientsDetails.IndexOf(((RegisteredClient)rxMessage).Client) >= 0))
                            {
                                GroupClientsDetails.Add(((RegisteredClient)rxMessage).Client);
                                if (MOnRegisterClient != null && Group == ((RegisteredClient)rxMessage).Group)
                                    MOnRegisterClient.Invoke(this, new ServerRegisterEventArgs(((RegisteredClient)rxMessage).Client));
                            }
                            break;
                        }
                    case ((int)MessageType.UnregisterMessage):
                        {
                            if ((GroupClientsDetails.IndexOf(((UnregisteredClient)rxMessage).Client) >= 0))
                            {
                                GroupClientsDetails.Remove(((UnregisteredClient)rxMessage).Client);
                                if (MOnRegisterClient != null && Group == ((UnregisteredClient)rxMessage).Group)
                                    MOnRegisterClient.Invoke(this, new ServerRegisterEventArgs(((UnregisteredClient)rxMessage).Client));
                            }
                            break;
                        }
                    case ((int)MessageType.BingoMessage):
                        {
                            if (rxMessage.Type != ((int)MessageType.EmptyMessage))
                            {
                                var clientip = string.Empty;
                                if (rxMessage.GetType().Equals(typeof(TextMessage)) || rxMessage.GetType().Equals(typeof(BingoMessage)))
                                {
                                    clientip = ((BingoMessage)rxMessage).ClientIp;
                                }
                                if (MOnReceiveMessage != null)
                                    MOnReceiveMessage.Invoke(this, new ReceiveServerMessageEventArgs(rxMessage, clientip));
                            }
                            break;
                        }

                    default:
                        {
                            if (rxMessage.Type != ((int)MessageType.EmptyMessage))
                            {
                                var clientip = string.Empty;
                                if (rxMessage.GetType().Equals(typeof(TextMessage)))
                                {
                                    clientip = ((TextMessage)rxMessage).ClientIp;
                                }
                                if (MOnReceiveMessage != null)
                                    MOnReceiveMessage.Invoke(this, new ReceiveServerMessageEventArgs(rxMessage, clientip));
                            }
                            break;
                        }
                }
            }
            catch (ObjectDisposedException ex)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs("Socket has been closed : " + ex.Message));
            }
            catch (SocketException ex)
            {
                if (ex.ErrorCode == 10054) // Error code for Connection reset by peer
                {
                    if (ServerReceiveError != null)
                        ServerReceiveError.Invoke(this, new ErrorEventArgs("Connection reset by peer : " + ex.Message));
                }
                else
                {
                    if (ServerReceiveError != null)
                        ServerReceiveError.Invoke(this, new ErrorEventArgs(ex.Message));
                }
            }
        } 
        #endregion

        #region Send messages to Client
        public void SendMessage(string ip, int port, IMessage message)
        {
            var _mClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress remoteMachine = IPAddress.Parse(ip);
            var remoteEndpoint = new IPEndPoint(remoteMachine, port);
            try
            {
                _mClientSocket.Connect(remoteEndpoint);
            }
            catch (Exception ex)
            {
                if (ServerReceiveError != null)
                    ServerReceiveError.Invoke(this, new ErrorEventArgs("An error occurred while attempting to connnect : " + ex.Message));
                return;
            }
            if (_mClientSocket.Connected)
                _mClientSocket.Send(message.GetMessagePacket());
            _mClientSocket.Close();
        } 
        #endregion
    }
}